﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;

namespace _80X_CG_Frame.Protocols
{
    public class OMRON_PLC
    {
        /// <summary>
        /// FINS协议头
        /// </summary>
        static string header = "46494E53";

        static string receive_pattern = @"^(?<header>.{8})(?<length>.{8})(?<command>.{8})(?<error_code>.{8}).*$";
        static Regex receive_regex = new Regex(receive_pattern, RegexOptions.IgnoreCase);
        /// <summary>
        /// Fins指令集
        /// </summary>
        public struct Fins_command
        {
            //读I/O内存区域
            public static string Memory_area_read = "0101";
            //写I/O内存区域
            public static string Memory_area_write = "0102";
            //填充I/O内存区域
            public static string Memory_area_fill = "0103";
            //写多个I/O内存区域
            public static string Mutiple_Memory_area_write = "0104";
            //迁移I/O内存区域
            public static string Mutiple_Memory_area_transfer = "0105";
        }

        /// <summary>
        /// 内存区域枚举
        /// </summary>
        public enum Memory
        {
            CIO,
            WR,
            HR,
            AR,
            DM
        }
        /// <summary>
        /// 内存区域位操作地址
        /// </summary>
        static Dictionary<Memory, string> memory_bit_dic = new Dictionary<Memory, string>
        {
            {Memory.CIO,  "30"},
            {Memory.WR,  "31"},
            {Memory.HR,  "32"},
            {Memory.AR,  "33"},
            {Memory.DM,  "02"}
        };
        /// <summary>
        /// 内存区域字操作地址
        /// </summary>
        static Dictionary<Memory, string> memory_word_dic = new Dictionary<Memory, string>
        {
            {Memory.CIO,  "b0"},
            {Memory.WR,  "b1"},
            {Memory.HR,  "b2"},
            {Memory.AR,  "b3"},
            {Memory.DM,  "82"}
        };
        /// <summary>
        /// 错误码
        /// </summary>
        static Dictionary<string, string> error_code = new Dictionary<string, string>
        {
            //{"00000000",  ""},//正常
            {"00000001",  "命令头部不是 'FINS' 的ASCII码"},
            {"00000002",  "数据太长"},
            {"00000003",  "不支持的命令"},
            {"00000020",  "所有连接都已被使用"},
            {"00000021",  "指定节点已被使用"},
            {"00000022",  "尝试使用未指定的IP地址访问被保护的节点"},
            {"00000023",  "客户端FINS节点地址超出范围"},
            {"00000024",  "客户端和服务端使用了相同的FINS节点地址"},
            {"00000025",  "所有可分配节点均已被使用"}
        };

        /// <summary>
        /// PLC节点
        /// </summary>
        public string Plc_node { get; set; }
        /// <summary>
        /// PC节点
        /// </summary>
        public string Pc_node { get; set; }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="plcnode">plc节点地址(0-254)</param>
        /// <param name="pcnode">电脑节点地址(0-254)</param>
        public OMRON_PLC(int plcnode, int pcnode)
        {
            Plc_node = plcnode.ToString("X2");
            Pc_node = pcnode.ToString("X2");
        }

        /*
        /// <summary>
        /// 
        /// </summary>
        /// <param name="plcnode">plc节点地址</param>
        /// <param name="pcnode">电脑节点地址</param>
        public OMRON_PLC(string plcnode, string pcnode)
        {
            Plc_node = plcnode;
            Pc_node = pcnode;
        }
        */

        /// <summary>
        /// 验证返回消息格式
        /// </summary>
        /// <param name="str">接收到的完整消息</param>
        /// <param name="error">错误码</param>
        /// <param name="command">命令码(非fins命令码)</param>
        /// <returns></returns>
        public static bool Validate(string str, ref string error, ref string command)
        {
            Match match = receive_regex.Match(str);
            if (match.Success)
            {
                string e = match.Groups["error_code"].Value;
                command = match.Groups["command"].Value;
                if (e != "00000000" && error_code.ContainsKey(e))
                {
                    error = error_code[e];
                    return false;
                }
                else if (e != "00000000")
                {

                    error = "未知的错误码:" + e;
                    return false;
                }
                int length = (Convert.ToInt32(match.Groups["length"].Value, 16) * 2) + 16;
                if (length != str.Length)
                {
                    error = "消息不完整或粘连";
                    return false;
                }

                return true;

            }
            return false;
        }

        /// <summary>
        /// 无返回数据结果的响应消息解析,如握手,写入
        /// </summary>
        /// <returns></returns>
        public bool NormalReturn(string str, ref string error)
        {
            string command = "";
            if (Validate(str, ref error, ref command))
            {
                string t_pc = "00";
                string t_plc = "00";
                switch (command)
                {
                    case "00000001"://握手
                        t_pc = str.Substring(38, 2);
                        t_plc = str.Substring(46, 2);
                        break;
                    case "00000002"://读取/写入
                        t_pc = str.Substring(40, 2);
                        t_plc = str.Substring(46, 2);
                        break;
                    default:
                        error = "无此响应消息处理逻辑";
                        return false;
                        break;
                }


                if (t_pc != Pc_node || t_plc != Plc_node)
                {
                    error = "PC或PLC节点不符合设定值";
                    return false;
                }

                return true;
            }
            else
                return false;
        }

        /// <summary>
        /// 读取位返回的响应消息解析方法
        /// </summary>
        /// <param name="str"></param>
        /// <param name="error"></param>
        /// <param name="result"></param>
        /// <returns></returns>
        public bool BitResultReturn(string str, ref string error, ref List<string> result)
        {
            if (result == null)
                result = new List<string>();
            string command = "";
            if (Validate(str, ref error, ref command))
            {
                string t_pc = "00";
                string t_plc = "00";
                if (command == "00000002")//读取/写入
                {
                    t_pc = str.Substring(40, 2);
                    t_plc = str.Substring(46, 2);
                    if (str.Length <= 60)
                    {
                        error = "消息解析失败";
                        return false;
                    }

                    string t_s = str.Substring(60);
                    for (int i = 0; i < t_s.Length; i += 2)
                    {
                        result.Add(t_s.Substring(i, 2));
                    }
                }
                else if (command == "00000001")
                {
                    error = "调用了错误的响应消息处理方法, 此方法不处理握手响应消息";
                    return false;
                }
                else
                {
                    error = "无此响应消息处理逻辑";
                    return false;
                }

                if (t_pc != Pc_node || t_plc != Plc_node)
                {
                    error = "PC或PLC节点不符合设定值";
                    return false;
                }

                return true;
            }
            else
                return false;
        }
        /// <summary>
        /// 读取字返回的响应消息解析方法
        /// </summary>
        /// <param name="str"></param>
        /// <param name="error"></param>
        /// <param name="result"></param>
        /// <returns></returns>
        public bool WordResultReturn(string str, ref string error, ref List<string> result)
        {
            if (result == null)
                result = new List<string>();
            string command = "";
            if (Validate(str, ref error, ref command))
            {
                string t_pc = "00";
                string t_plc = "00";
                if (command == "00000002")//读取/写入
                {
                    t_pc = str.Substring(40, 2);
                    t_plc = str.Substring(46, 2);
                    if (str.Length <= 60)
                    {
                        error = "消息解析失败";
                        return false;
                    }

                    string t_s = str.Substring(60);
                    for (int i = 0; i < t_s.Length; i += 4)
                    {
                        result.Add(t_s.Substring(i, 4));
                    }
                }
                else if (command == "00000001")
                {
                    error = "调用了错误的响应消息处理方法, 此方法不处理握手响应消息";
                    return false;
                }
                else
                {
                    error = "无此响应消息处理逻辑";
                    return false;
                }

                if (t_pc != Pc_node || t_plc != Plc_node)
                {
                    error = "PC或PLC节点不符合设定值";
                    return false;
                }

                return true;
            }
            else
                return false;
        }

        public bool DWordResultReturn(string str, ref string error, ref List<float> result)
        {
            if (result == null)
                result = new List<float>();
            var datas = new List<string>();
            var ret = WordResultReturn(str, ref error, ref datas);
            if (ret)
            {
                if (datas.Count % 2 == 0)
                {
                    for (int i = 0; i < datas.Count; i += 2)
                    {
                        byte[] bs = ConvertBytes(datas[i] + datas[i + 1]);
                        var t = new byte[] { bs[1], bs[0], bs[3], bs[2] };
                        result.Add(BitConverter.ToSingle(t, 0));
                    }
                }
            }

            return ret;
        }
        /// <summary>
        /// 以位形式读plc的I/O内存
        /// </summary>
        /// <param name="area">内存区域</param>
        /// <param name="start">起始地址</param>
        /// <param name="length">读取长度</param>
        /// <param name="message">指令结果</param>
        /// <param name="offset">位偏移</param>
        /// <returns></returns>
        public bool ReadIOMemoryBit(Memory area, int start, int offset, int length, ref string message)
        {
            string data = "000000020000000080000200"
                + Plc_node + "0000"
                + Pc_node + "00FF"
                + Fins_command.Memory_area_read
                + memory_bit_dic[area]
                + start.ToString("X4")
                + offset.ToString("X2")
                + length.ToString("X4");
            message = header + (data.Length / 2).ToString("X8") + data;

            return true;
        }

        /// <summary>
        /// 以字节形式读plc的I/O内存
        /// </summary>
        /// <param name="area">内存区域</param>
        /// <param name="start">起始地址</param>
        /// <param name="length">读取长度</param>
        /// <param name="message">指令结果</param>
        /// <returns></returns>
        public bool ReadIOMemoryWord(Memory area, int start, int length, ref string message)
        {
            string data = "000000020000000080000200"
                + Plc_node + "0000"
                + Pc_node + "00FF"
                + Fins_command.Memory_area_read
                + memory_word_dic[area]
                + start.ToString("X4")
                + "00"
                + length.ToString("X4");
            message = header + (data.Length / 2).ToString("X8") + data;

            return true;
        }

        /// <summary>
        /// 以位形式写plc的I/O内存
        /// </summary>
        /// <param name="area">内存区域</param>
        /// <param name="start">起始地址</param>
        /// <param name="datas">要写入的数据</param>
        /// <param name="message">指令结果</param>
        /// <param name="offset">位偏移</param>
        /// <returns></returns>
        public bool WriteIOMemoryBit(Memory area, int start, int offset, List<string> datas, ref string message)
        {
            string data = "000000020000000080000200"
                + Plc_node + "0000"
                + Pc_node + "00FF"
                + Fins_command.Memory_area_write
                + memory_bit_dic[area]
                + start.ToString("X4")
                + offset.ToString("X2")
                + datas.Count.ToString("X4")
                + string.Concat(datas.Select(s => s.PadLeft(2, '0')));
            message = header + (data.Length / 2).ToString("X8") + data;

            return true;
        }

        /// <summary>
        /// 以字节形式写plc的I/O内存
        /// </summary>
        /// <param name="area">内存区域</param>
        /// <param name="start">起始地址</param>
        /// <param name="datas">要写入的数据</param>
        /// <param name="message">指令结果</param>
        /// <returns></returns>
        public bool WriteIOMemoryWord(Memory area, int start, List<string> datas, ref string message)
        {
            string data = "000000020000000080000200"
                + Plc_node + "0000"
                + Pc_node + "00FF"
                + Fins_command.Memory_area_write
                + memory_word_dic[area]
                + start.ToString("X4")
                + "00"
                + datas.Count.ToString("X4")
                + string.Concat(datas.Select(s => s.PadLeft(4, '0')));
            message = header + (data.Length / 2).ToString("X8") + data;

            return true;
        }

        public bool WriteIOMemoryDWord(Memory area, int start, List<float> datas, ref string message)
        {
            if (datas.Count == 0)
            {
                return false;
            }
            var result = false;
            var word_datas = new List<string>();
            foreach (var d in datas)
            {
                byte[] bs = BitConverter.GetBytes(d);
                word_datas.Add(bs[1].ToString("X2") + bs[0].ToString("X2"));
                word_datas.Add(bs[3].ToString("X2") + bs[2].ToString("X2"));
            }
            WriteIOMemoryWord(area, start, word_datas, ref message);
            return result;
        }
        /// <summary>
        /// 握手
        /// </summary>
        /// <param name="message"></param>
        /// <returns></returns>
        public bool ShakeHand(ref string message)
        {

            string data = "0000000000000000000000"
                + Pc_node;
            message = header + (data.Length / 2).ToString("X8") + data;
            return true;
        }


        public static byte[] ConvertBytes(string str)
        {
            byte[] code = new byte[str.Length / 2];

            for (int i = 0; i < str.Length; i += 2)
            {
                code[i / 2] = Convert.ToByte(str.Substring(i, 2), 16);
            }

            return code;

        }

    }


}
